#include "precomp.h"

namespace Tmpl {

// ================================
// Shader
// ================================

Shader::Shader()
{
	m_Name[0] = 0;
	m_Compiled = 0;
	m_Handle = 0;
}
Shader::~Shader()
{
	if (glIsShader(m_Handle)) glDeleteShader(m_Handle);
}

void Shader::Compile()
{
	// Create a shader object
    m_Handle = glCreateShader(GetShaderType());

	// Upload shader code to OpenGL
	const GLchar *code = m_Code.c_str();
	glShaderSource(m_Handle, 1, &code, NULL);

	// Compile shader
	glCompileShader(m_Handle);
	glGetShaderiv(m_Handle, GL_COMPILE_STATUS, &m_Compiled);
	if (m_Compiled == GL_FALSE)
	{
		int bufflen;
		glGetShaderiv(m_Handle, GL_INFO_LOG_LENGTH, &bufflen);

		char* buffer = new char[bufflen];
		glGetShaderInfoLog( m_Handle, bufflen, 0, &buffer[0] );

		LOG_ERROR("Shader Compile Error: %s\n%s", m_Name, buffer);
	
		delete [] buffer;
		return;
	}

	#ifdef VALIDATE_SHADERS
	int len;
	char msg[8192];
	glGetShaderInfoLog( m_Handle, 8192, &len, &msg[0] );
	INFO_SHADER("Shader Validation (%s) %s", m_Name, msg)
	#endif
}


void Shader::LoadShaderFile(const char* a_Filename)
{
	strcpy_s(m_Name, a_Filename);

	// Open the file
	std::ifstream ifs(a_Filename, std::ios::in | std::ios::binary);
	if (ifs.fail())
	{
		LOG_ERROR("Shader - Could not load: %s", a_Filename );
		return;
	}

	// Read whole file into string
	m_Code.assign(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());

	// Close file
	ifs.close();

	// Compile the shader
	Compile();
}

// ================================
// ShaderProgram
// ================================

ShaderProgram::ShaderProgram()
{
	m_Handle = 0;
	m_VertexShader = 0;
	m_FragmentShader = 0;
	m_Linked = 0;
}
ShaderProgram::~ShaderProgram()
{
	if (glIsProgram(m_Handle)) glDeleteProgram(m_Handle);

	// delete children
	delete m_VertexShader;
	delete m_FragmentShader;
}

void ShaderProgram::LoadShader(const char* a_VertexShader, const char* a_FragmentShader)
{
	// Check for old shader first
	delete m_VertexShader;
	delete m_FragmentShader;
	if (glIsProgram(m_Handle)) glDeleteProgram(m_Handle);
	m_Handle = 0;

	// Load Vertex Shader
	m_VertexShader = new VertexShader();
	m_VertexShader->LoadShaderFile(a_VertexShader);
	if (m_VertexShader->HasFailed()) return;

	// Load Fragment Shader
	m_FragmentShader = new FragmentShader();
	m_FragmentShader->LoadShaderFile(a_FragmentShader);
	if (m_FragmentShader->HasFailed()) return;

	// Create program and attach vertex and fragment shaders
	m_Handle = glCreateProgram();
	glAttachShader(m_Handle, m_VertexShader->GetHandle());
	glAttachShader(m_Handle, m_FragmentShader->GetHandle());

	//#ifdef VALIDATE_SHADERS
	//glValidateProgram(m_Handle);
	//int status;
	//glGetProgramiv(m_Handle, GL_VALIDATE_STATUS, &status);
	//if (status == GL_FALSE)
	//{
	//	int bufflen;
	//	glGetProgramiv(m_Handle, GL_INFO_LOG_LENGTH, &bufflen);

	//	char* buffer = new char[bufflen];
	//	glGetProgramInfoLog( m_Handle, bufflen, 0, &buffer[0] );

	//	TRACE_SHADER("Shader Validate Failed:\n\nVertexShader: %s\nFragmentShader: %s\n\n%s", a_VertexShader, a_FragmentShader, buffer);

	//	delete buffer;
	//	return;
	//}
	//#endif

	// Perform link stage
	glLinkProgram(m_Handle);
	glGetProgramiv(m_Handle, GL_LINK_STATUS, &m_Linked);

	// If there was an error in compilation switch it
	if (m_Linked != GL_TRUE)
	{
		int bufflen;
		glGetProgramiv(m_Handle, GL_INFO_LOG_LENGTH, &bufflen);

		char* buffer = new char[bufflen];
		glGetProgramInfoLog( m_Handle, bufflen, 0, &buffer[0] );

		LOG_ERROR("Shader Linking Error:\n\nVertexShader: %s\nFragmentShader: %s\n\n%s", a_VertexShader, a_FragmentShader, buffer);
	
		delete [] buffer;

		delete m_VertexShader;
		delete m_FragmentShader;
		if (glIsProgram(m_Handle)) glDeleteProgram(m_Handle);
		m_VertexShader = 0;
		m_FragmentShader = 0;
		m_Handle = 0;
	}
}

GLint ShaderProgram::GetUniformLoc(const char* a_Name)
{
	std::string v = a_Name;
	LocMap::iterator it = m_UniformLocMap.find(v);
	if(it == m_UniformLocMap.end())
	{
		const GLint loc = glGetUniformLocation(m_Handle, a_Name);
		m_UniformLocMap.insert(std::pair<std::string, GLint>(v, loc));
		return loc;
	}
	else
	{
		return it->second;
	}
}

GLint ShaderProgram::GetAttribLoc(const char* a_Name)
{
	std::string v = a_Name;
	LocMap::iterator it = m_AttribLocMap.find(v);
	if(it == m_AttribLocMap.end())
	{
		const GLint loc = glGetAttribLocation(m_Handle, a_Name);
		m_AttribLocMap.insert(std::pair<std::string, GLint>(v, loc));
		return loc;
	}
	else
	{
		return it->second;
	}
}


void ShaderProgram::Uniform1i(const char* a_Name, GLint a_Val)
{
	const GLint loc = GetUniformLoc(a_Name);
	glUniform1i(loc, a_Val);
}

void ShaderProgram::Uniform1f(const char* a_Name, float a_Val)
{
	const GLint loc = GetUniformLoc(a_Name);
	 glUniform1f(loc, a_Val);
}

void ShaderProgram::Uniform2f(const char* a_Name, float a_Val1, float a_Val2)
{
	const GLint loc = GetUniformLoc(a_Name);
	glUniform2f(loc, a_Val1, a_Val2);
}

void ShaderProgram::Uniform3f(const char* a_Name, float a_Val1, float a_Val2, float a_Val3)
{
	const GLint loc = GetUniformLoc(a_Name);
	glUniform3f(loc, a_Val1, a_Val2, a_Val3);
}

void ShaderProgram::Uniform3f(const char* a_Name, Vec3f a_Val)
{
	const GLint loc = GetUniformLoc(a_Name);
	glUniform3f(loc, a_Val.x, a_Val.y, a_Val.z);
}

void ShaderProgram::Uniform4f(const char* a_Name, float a_Val1, float a_Val2, float a_Val3, float a_Val4)
{
	const GLint loc = GetUniformLoc(a_Name);
	glUniform4f(loc, a_Val1, a_Val2, a_Val3, a_Val4);
}

void ShaderProgram::UniformMatrix4x4(const char* a_Name, float* a_Matrix)
{
	const GLint loc = GetUniformLoc(a_Name);
	glUniformMatrix4fv(loc, 1, false, a_Matrix);
}

void ShaderProgram::UniformMatrix2x2( const char* a_Name, float* a_Matrix)
{
	const GLint loc = GetUniformLoc(a_Name);
	glUniformMatrix2fv(loc, 1, false, a_Matrix);
}

void ShaderProgram::Use()
{
	glUseProgram(m_Handle);
}
void ShaderProgram::UnUse()
{
	glUseProgram(0);
}

// ================================
// Shader Manager
// ================================

std::map<std::string, ShaderProgram*> ShaderManager::m_Shaders;
ShaderProgram* ShaderManager::LoadShader(const char* a_FileName)
{
	const std::string fileName(a_FileName);
	
	// Look if shader is already loaded in
	ShaderMap::iterator it = m_Shaders.find( fileName );
	if(it != m_Shaders.end())
	{
		return it->second;
	}

	// Load
	LOG_PRINT("Shader - Loading: %s", a_FileName);
	ShaderProgram* program = new ShaderProgram();
	program->LoadShader( (fileName + ".vert").c_str(), (fileName + ".frag").c_str() );
	m_Shaders.insert( ShaderMap::value_type( fileName, program ) );
	return program;
}

void ShaderManager::Reload()
{
	std::map<std::string, ShaderProgram*> copy = m_Shaders;
	m_Shaders.clear();
	for (std::map<std::string, ShaderProgram*>::iterator i = copy.begin(); i != copy.end(); i++)
	{
		LoadShader(i->first.c_str());
	}
}



}; // namespace Relativity